package org.smartboot.flow.core.metrics;

import org.smartboot.flow.core.EngineContext;
import org.smartboot.flow.core.ExecutionListener;
import org.smartboot.flow.core.ExecutionListenerSupport;
import org.smartboot.flow.core.Key;
import org.smartboot.flow.core.Measurable;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author qinluo
 * @date 2022-11-25 21:23:28
 * @since 1.0.0
 */
public class MetricExecutionListener extends ExecutionListenerSupport {

    private static final ExecutionListener INSTANCE = new MetricExecutionListener();

    public static ExecutionListener getInstance() {
        return INSTANCE;
    }

    @Override
    public <T, S> void beforeExecute(EngineContext<T, S> context, Object object) {
        if (!(object instanceof Measurable)) {
            return;
        }

        Map<Object, Long> escaped = context.getExt(Key.of(this));
        if (escaped == null) {
            escaped = new ConcurrentHashMap<>();
            context.putExt(Key.of(this), escaped);
        }


        Measurable measurable = (Measurable) object;
        Metrics metrics = measurable.getMetrics();
        metrics.addMetric(NamedMetrics.EXECUTE, 1);
        escaped.put(object, System.currentTimeMillis());
    }

    @Override
    public <T, S> void afterExecute(EngineContext<T, S> context, Object object) {
        if (!(object instanceof Measurable)) {
            return;
        }

        Map<Object, Long> escaped = context.getExt(Key.of(this));
        if (escaped == null) {
            return;
        }

        Long start = escaped.remove(object);
        if (start == null) {
            return;
        }

        Measurable measurable = (Measurable) object;
        Metrics metrics = measurable.getMetrics();
        if (context.getFatal() != null) {
            metrics.addMetric(NamedMetrics.FAIL, 1);
        }
        long now = System.currentTimeMillis();
        metrics.addMetric(NamedMetrics.TOTAL_ESCAPE, (now - start));
        metrics.addMetric(MetricKind.MAX, NamedMetrics.MAX_ESCAPE, (now - start));
    }

    @Override
    public <T, S> void beforeRollback(EngineContext<T, S> context, Object object) {
        if (!(object instanceof Measurable)) {
            return;
        }

        Map<Object, Long> escaped = context.getExt(Key.of(this));
        if (escaped == null) {
            return;
        }

        Measurable measurable = (Measurable) object;
        Metrics metrics = measurable.getMetrics();
        metrics.addMetric(NamedMetrics.ROLLBACK, 1);
        escaped.put(object, System.currentTimeMillis());
    }

    @Override
    public <T, S> void afterRollback(EngineContext<T, S> context, Object object) {
        if (!(object instanceof Measurable)) {
            return;
        }

        Map<Object, Long> escaped = context.getExt(Key.of(this));
        if (escaped == null) {
            return;
        }

        Long start = escaped.remove(object);
        if (start == null) {
            return;
        }

        Measurable measurable = (Measurable) object;
        Metrics metrics = measurable.getMetrics();
        long now = System.currentTimeMillis();
        metrics.addMetric(NamedMetrics.ROLLBACK_TOTAL_ESCAPE, (now - start));
        metrics.addMetric(MetricKind.MAX, NamedMetrics.ROLLBACK_MAX_ESCAPE, (now - start));
    }
}
